<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <link href="https://cdn.bootcdn.net/ajax/libs/normalize/7.0.0/normalize.min.css" rel="stylesheet">
</head>
<body>
<h2>策略模式</h2>
<h3>1. 定义</h3>
<p>定义一系列的算法，把它们一个个封装起来，并且使它们可以相互替换。</p>

<h3>2. 核心</h3>
<p>将算法的使用和算法的实现分离开来。<br>
一个基于策略模式的程序至少由两部分组成：<br>
第一个部分是一组策略类，策略类封装了具体的算法，并负责具体的计算过程。<br>
第二个部分是环境类Context，Context接受客户的请求，随后把请求委托给某一个策略类。要做到这点，说明Context 中要维持对某个策略对象的引用</p>


<h3>3. 优缺点</h3>
<p>优点：可以有效地避免多重条件语句，将一系列方法封装起来也更直观，利于维护</p>
<p>缺点：往往策略集会比较多，我们需要事先就了解定义好所有的情况</p>


<h3>应用场景</h3>
<p>1）form表单验证Validator  </p>

<form action="http:// xxx.com/register" id="registerForm" method="post">
    <p>请输入用户名：<input type="text" name="userName"/ ></p>
    <p>请输入密码：<input type="text" name="password"/ ></p>
    <p>请输入手机号码：<input type="text" name="phoneNumber"/ ></p>
    <p><button>提交</button></p>
</form>

<script type="text/javascript">
//3. 实现 
//策略模式可以用于组合一系列算法，也可用于组合一系列业务规则
//假设需要通过成绩等级来计算学生的最终得分，每个成绩等级有对应的加权值。我们可以利用对象字面量的形式直接定义这个组策略

// 加权映射关系
var levelMap = {
    'S': 10,
    'A': 8,
    'B': 6,
    'C': 4
};

// 组策略
var scoreLevel = {
    basicScore: 80,
    S: function () {
        return this.basicScore + levelMap['S']; 
    },
    A: function () {
        return this.basicScore + levelMap['A']; 
    },
    B: function () {
        return this.basicScore + levelMap['B']; 
    },
    C: function () {
        return this.basicScore + levelMap['C']; 
    }
}

//调用
function getScore(level) {
    return scoreLevel[level] ? scoreLevel[level]() : 0
}

//console.log( getScore('S') )

//===============================================
//在组合业务规则方面，比较经典的是表单的验证方法。这里列出比较关键的部分
var errorMsgs = {
    'default' : '数据格式不对',
    'required': '不能为空',
    'minLength': '输入数据长度不足',
    'isNumber': '请输入数字',
    'isMobile': '手机号码格式不对'
}
var rules = {
    'minLength': function (val, len, errMsg) {
        if (val.length < len) {
            return errMsg || errorMsgs['minLength']
        }
    },
    'isNumber': function (val, errMsg) {
        if (!/\d+/.test(val)) {
            return errMsg || errorMsgs['isNumber']
        }
    },
    'isMobile': function (val, errMsg) {
        if (!/(^1[3-9]\d{9}$)/.test(val)) {
            return errMsg || errorMsgs['isMobile']
        }
    },
    'required': function (val, errMsg) {
        if (val === '') {
            return errMsg || errorMsgs['required']
        }
    }
}

//验证器
class Validator {
    constructor() {
        this.cache = []     //保存校验规则
    }
    add( dom, rulesArr) {
        // let self = this;
        for (let i = 0, ruleItem; ruleItem = rulesArr[i++];) {

            let arg = [dom.value];
            //将校验规则对象中的属性的值进行分割
            const temp = ruleItem.rule.split(':');
            const rule = temp[0];

            if (ruleItem.rule.indexOf('minLength') >= 0) {
                arg.push(temp[1])
            }

            arg.push(ruleItem.errMsg)

            this.cache.push( function() {
                return rules[ rule ].apply(dom, arg)
            } )
        }
    }
    start() {
        for (var i = 0; i < this.cache.length; i++) {
            var ret = this.cache[i]()
            if ( ret ){
                return ret;
            }
        }
    }
}

// 测试数据
function testTel(val) {
    return val;
}

/*var validate = new Validator();

validate.add( testTel('ccc'), 'isNumber', '只能为数字'); // 只能为数字
validate.add( testTel(''), 'required'); // 内容不为空
validate.add( testTel('123'), 'minLength:5', '最少5位'); // 最少5位
validate.add( testTel('12345'), 'minLength:5', '最少5位');

validate.start();*/


//调用校验
var registerForm = document.getElementById( 'registerForm' );

var validataFunc = function(){
    //实例化对象
    var validator = new Validator();
    //自定义添加校验规则
    validator.add( registerForm.userName, [{
        rule: 'required',
        errMsg: '用户名不能为空'
    }, {
        rule: 'minLength:6',
        errMsg: '用户名长度不能小于10 位'
    }]);
    validator.add( registerForm.password, [{
        rule: 'minLength:6',
        errMsg: '密码长度不能小于6 位'
    }]);
    //调用方法循环执行校验
    var ret = validator.start();
    return ret;
}

//点击提交按钮（提交事件）
registerForm.onsubmit = function(e){
    //执行上面自定义的校验方法
    var errMsg = validataFunc();
    console.log(errMsg)
    //如果errMsg存在，即代表校验没有通过
    if ( errMsg ){
        alert ( errMsg );
        return false;
    }

};
</script>
</body>
</html>